package edu.ustb.efws.meter.service;

import edu.ustb.efws.EfwsProperties;
import edu.ustb.efws.base.domain.Position;
import edu.ustb.efws.base.service.PositionService;
import edu.ustb.efws.meter.domain.Meter;
import edu.ustb.efws.meter.mapper.MeterMapper;
import edu.ustb.efws.pipeline.collect.handler.meter.MeterPoint;
import edu.ustb.efws.pipeline.service.TableOptService;
import edu.ustb.efws.utils.DateUtils;
import edu.ustb.efws.utils.SafeMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

/**
 * 创建测量服务
 *
 * @author TS Group
 */
@Service
@Transactional(readOnly = true)
public class MeterService {
    private static final Logger LOGGER = LoggerFactory.getLogger(MeterService.class);

    private final EfwsProperties properties;
    private final MeterMapper mapper;
    private final PositionService positionService;
    private final TableOptService tableOptService;

    @Autowired
    public MeterService(EfwsProperties properties, MeterMapper mapper,
                        PositionService positionService, TableOptService tableOptService) {
        this.properties = properties;
        this.mapper = mapper;
        this.positionService = positionService;
        this.tableOptService = tableOptService;
    }

    /**
     * 创建表
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createTable(){
        try{
            List<Position> positions = positionService.all();
            positions.stream().flatMapToInt(e -> Arrays.stream(positionIds(e.getId()))).forEach(positionId -> {
                tableOptService.createIfAbsent(buildTableName(positionId), n -> {
                    mapper.createTable(positionId);
                    //创建表分区
                    tableOptService.createPartition(n, properties.getStartMonth(), (m, p) -> mapper.createPartition(positionId, m, p));
                });
            });
        }catch (Exception e){
            LOGGER.error("Create meter table fail error={}", e.getMessage());
        }
    }

    private int[] positionIds(int positionId){
        return (positionId >= 50 && positionId <= 58)? new int[]{positionId, positionId * 10}: new int[]{positionId};
    }

    private String buildTableName(int positionId){
        return String.format("clg_meter_%d", positionId);
    }

    /**
     * 创建分区
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createPartition(){
        try{
            List<Position> positions = positionService.all();
            positions.stream().flatMapToInt(e -> Arrays.stream(positionIds(e.getId()))).forEach(positionId -> {
                tableOptService.createPartition(buildTableName(positionId), properties.getStartMonth(),
                        (m, p) -> mapper.createPartition(positionId, m, p));
            });
        }catch (Exception e){
            LOGGER.error("Create meter table partition fail error={}", e.getMessage());
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public boolean update(MeterPoint point){
        Meter t = newMeter(point);
        return mapper.update(t) > 0;
    }

    private Meter newMeter(MeterPoint point){
        long timestamp = point.getIndex() * properties.getWindowTimeMin() * 60 * 1000L;
        LocalDateTime localDateTime = DateUtils.dateToLocalDataTime(new Date(timestamp));

        int month = localDateTime.getYear() * 100 + localDateTime.getMonthValue();
        int day = localDateTime.getDayOfMonth();
        int hour = localDateTime.getHour();
        int minute = localDateTime.getMinute();

        Meter t = new Meter();
        t.setPositionId(point.getPositionId());
        t.setMonth(month);
        t.setDay(day);
        t.setHour(hour);
        t.setMinute(minute);
        t.setStartScore(point.getStartScore());
        t.setEndScore(point.getEndScore());
        t.setMaxScore(point.getMaxScore());
        t.setMinScore(point.getMinScore());
        t.setTotalScore(point.getTotalScore());
        t.setCount(point.getCount());
        float score = point.getTotalScore() / (point.getCount() + 1);
        t.setScore(score);
        t.setGrade(SafeMath.riskGrade(score));

        return t;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void save(MeterPoint point){
        Meter meter = newMeter(point);
        mapper.insert(meter);
    }

    public List<Meter> queryDay(int positionId, int month, int day){
        return mapper.findDay(positionId, month, day);
    }

    public List<Meter> queryMonth(int positionId, int month){
        int to = month + 1;
        List<Meter> meterAggrLst = mapper.findDayAggr(positionId, month, to);
        List<Meter> meterSELst = mapper.findDayStartEnd(positionId, month, to);
        merge(meterAggrLst, meterSELst, (aMeter, sMeter) ->
                aMeter.getMonth().intValue() == sMeter.getMonth() && aMeter.getDay().intValue() == sMeter.getDay());
        return meterAggrLst;
    }

    private void merge(List<Meter> aggrLst, List<Meter> seLst, BiFunction<Meter, Meter, Boolean> matchFun){
        for(Meter meter: aggrLst){
            List<Meter> se = getSE(seLst, meter, matchFun);
            if(se.isEmpty()){
                continue;
            }

            if(se.size()==1){
                meter.setStartScore(se.get(0).getStartScore());
                meter.setEndScore(se.get(0).getEndScore());
            }else{
                meter.setStartScore(se.get(0).getStartScore());
                meter.setEndScore(se.get(1).getEndScore());
            }
            meter.setGrade(SafeMath.riskGrade(meter.getScore()));
        }
    }

    private List<Meter> getSE(List<Meter> seLst, Meter aggrMeter, BiFunction<Meter, Meter, Boolean> matchFun){
        return seLst.stream()
                .filter(e -> matchFun.apply(aggrMeter, e))
                .collect(Collectors.toList());
    }

    public List<Meter> queryYear(int positionId, int year){
        int from = year * 100 + 1;
        int to = (year + 1) * 100 + 1;
        List<Meter> meterAggrLst = mapper.findMonthAggr(positionId, from, to);
        List<Meter> meterSELst = mapper.findMonthStartEnd(positionId, from, to);
        merge(meterAggrLst, meterSELst, (aMeter, sMeter) -> aMeter.getMonth().intValue() == sMeter.getMonth());
        return meterAggrLst;
    }
}
